#include "renderer/renderer.h"

#include <iostream>
#include <math.h>

#include <gl/glew.h>
#include <ft2build.h>

#include "matrixstack/matrixstack.h"

#include "renderer/camera.h"
#include "renderer/entityrenderer.h"
#include "renderer/guirenderer.h"
#include "renderer/subrenderer.h"
#include "renderer/terrainrenderer.h"

Renderer *renderer;

Renderer::Renderer(){
    camera = new Camera();
    renderMode = RM_DRAW;
    font = new Font();
}

void Renderer::Initialise(){
    width = 1600;
    height = 900;
    frustumScale = 1.78;
    zNear = 0.1;
    zFar = 40;
	glShadeModel(GL_SMOOTH);
	glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
	glClearDepth(1.0f);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    terrainRenderer = std::shared_ptr<TerrainRenderer>(new TerrainRenderer(width, height, frustumScale, zNear, zFar));
    terrainRenderer->LoadTerrain();
    subRenderers.push_back(terrainRenderer);
    entityRenderer = std::shared_ptr<EntityRenderer>(new EntityRenderer(width, height, frustumScale, zNear, zFar));
    subRenderers.push_back(entityRenderer);
    guiRenderer = std::shared_ptr<GuiRenderer>(new GuiRenderer(width, height, frustumScale, zNear, zFar));
    subRenderers.push_back(guiRenderer);

    for(auto sub:subRenderers){
        sub->SetCameraPosition(camera->Getx(), camera->Gety(), camera->Getz());
        sub->SetCameraAngle(camera->GetRotationRadiansx());
    }
    SetupPickingFramebuffer();
}

void Renderer::RefreshTerrain(){
    terrainRenderer->LoadTerrain();
}

void Renderer::SetupPickingFramebuffer(){
    GLuint rbo;

    glGenFramebuffers(1, &pickingFBO);
    glBindFramebuffer(GL_FRAMEBUFFER, pickingFBO);
    if(glIsFramebuffer(pickingFBO)==GL_FALSE){
        std::cout<<"Failed to create frame buffer\n";
        return;
    }
    glGenRenderbuffers(1, &rbo);
    glBindRenderbuffer(GL_RENDERBUFFER, rbo);
    if(glIsRenderbuffer(rbo)==GL_FALSE){
        std::cout<<"Failed to create render buffer\n";
        return;
    }
    ///TODO Currently it renders to the whole scene, better to point camera in direction of click and just render one pixel
    glRenderbufferStorage(GL_RENDERBUFFER, GL_RGB, 1600, 900);
    if(glGetError()==GL_INVALID_VALUE){
        std::cout<<"Error allocating render buffer storage\n";
        return;
    }
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rbo);

    if(glCheckFramebufferStatus(GL_FRAMEBUFFER)==GL_FRAMEBUFFER_COMPLETE){
        std::cout<<"Picking framebuffer: OK\n";
    }
    else{
        std::cout<<"Picking framebuffer: Failed\n";
    }
    glBindFramebuffer(GL_FRAMEBUFFER, 0);
}

void Renderer::ProcessKey(int key){
    if(key=='P'){
        renderMode = renderMode==RM_DRAW ? RM_PICK : RM_DRAW;
    }

    camera->ProcessKey(key);
    for(auto sub:subRenderers){
        sub->SetCameraPosition(camera->Getx(), camera->Gety(), camera->Getz());
        sub->SetCameraAngle(camera->GetRotationRadiansx());
    }
}

void Renderer::Draw(){
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    guiStack->Reset();
    if(renderMode==RM_DRAW){
        terrainRenderer->Draw();
        entityRenderer->Draw();
        guiRenderer->Draw();
    }
    else{
        Pick(0,0);
    }
}

int Renderer::Pick(int x, int y){
    float data[3];
    int pickedValue;

    glBindFramebuffer(GL_FRAMEBUFFER, pickingFBO);
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

    terrainRenderer->Pick();
    entityRenderer->Pick();
    guiRenderer->Pick();

    glReadPixels(x, y, 1, 1, GL_RGB, GL_FLOAT, data);

    pickedValue = round(data[0]*255) + round(data[1]*255)*255 + round(data[2]*255)*255*255;
//    std::cout<<"{"<<x<<", "<<y<<"} : ["<<pickedValue<<"]\n";

    glBindFramebuffer(GL_FRAMEBUFFER, 0);
    return pickedValue;
}


void Renderer::Resize(int width, int height){
	if (height==0){
		height=1;
	}
	scenex = width;
	sceney = height;

	terrainRenderer->Resize(width, height);
	entityRenderer->Resize(width, height);

	glViewport(0,0,width,height);
}

void GLFWCALL Renderer::ResizeCallback(int w, int h){
    renderer->Resize(w, h);
}

void Renderer::Kill(){
//    font.clean();
//    lua_close(L);
}

